﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


// This file defines an internal class used to throw exceptions in BCL code.
// The main purpose is to reduce code size.
//
// The old way to throw an exception generates quite a lot IL code and assembly code.
// Following is an example:
//     C# source
//          throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
//     IL code:
//          IL_0003:  ldstr      "key"
//          IL_0008:  ldstr      "ArgumentNull_Key"
//          IL_000d:  call       string System.Environment::GetResourceString(string)
//          IL_0012:  newobj     instance void System.ArgumentNullException::.ctor(string,string)
//          IL_0017:  throw
//    which is 21bytes in IL.
//
// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
// argument name and resource name in a small integer. The source code will be changed to
//    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
//
// The IL code will be 7 bytes.
//    IL_0008:  ldc.i4.4
//    IL_0009:  ldc.i4.4
//    IL_000a:  call       void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
//    IL_000f:  ldarg.0
//
// This will also reduce the Jitted code size a lot.
//
// It is very important we do this for generic classes because we can easily generate the same code
// multiple times for different instantiation.
//

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;

namespace AtomUI.Collections.Pooled;

internal static class ThrowHelper
{
   [DoesNotReturn]
   internal static void ThrowArrayTypeMismatchException()
   {
      throw new ArrayTypeMismatchException();
   }

   [DoesNotReturn]
   internal static void ThrowIndexOutOfRangeException()
   {
      throw new IndexOutOfRangeException();
   }

   [DoesNotReturn]
   internal static void ThrowArgumentOutOfRangeException()
   {
      throw new ArgumentOutOfRangeException();
   }

   [DoesNotReturn]
   internal static void ThrowArgumentException_DestinationTooShort()
   {
      throw new ArgumentException("Destination too short.");
   }

   [DoesNotReturn]
   internal static void ThrowArgumentException_OverlapAlignmentMismatch()
   {
      throw new ArgumentException("Overlap alignment mismatch.");
   }

   [DoesNotReturn]
   internal static void ThrowArgumentOutOfRange_IndexException()
   {
      throw GetArgumentOutOfRangeException(ExceptionArgument.index,
                                           ExceptionResource.ArgumentOutOfRange_Index);
   }

   [DoesNotReturn]
   internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException()
   {
      throw GetArgumentOutOfRangeException(ExceptionArgument.index,
                                           ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
   }

   [DoesNotReturn]
   internal static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
   {
      throw GetArgumentOutOfRangeException(ExceptionArgument.value,
                                           ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
   }

   [DoesNotReturn]
   internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum()
   {
      throw GetArgumentOutOfRangeException(ExceptionArgument.length,
                                           ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
   }

   [DoesNotReturn]
   internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index()
   {
      throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex,
                                           ExceptionResource.ArgumentOutOfRange_Index);
   }

   [DoesNotReturn]
   internal static void ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count()
   {
      throw GetArgumentOutOfRangeException(ExceptionArgument.count,
                                           ExceptionResource.ArgumentOutOfRange_Count);
   }

   [DoesNotReturn]
   internal static void ThrowWrongKeyTypeArgumentException<T>(T key, Type targetType)
   {
      // Generic key to move the boxing to the right hand side of throw
      throw GetWrongKeyTypeArgumentException((object?)key, targetType);
   }

   [DoesNotReturn]
   internal static void ThrowWrongValueTypeArgumentException<T>(T value, Type targetType)
   {
      // Generic key to move the boxing to the right hand side of throw
      throw GetWrongValueTypeArgumentException((object?)value, targetType);
   }

   private static ArgumentException GetAddingDuplicateWithKeyArgumentException(object? key)
   {
      return new ArgumentException($"Error adding duplicate with key: {key}.");
   }

   [DoesNotReturn]
   internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
   {
      // Generic key to move the boxing to the right hand side of throw
      throw GetAddingDuplicateWithKeyArgumentException((object?)key);
   }

   [DoesNotReturn]
   internal static void ThrowKeyNotFoundException<T>(T key)
   {
      // Generic key to move the boxing to the right hand side of throw
      throw GetKeyNotFoundException((object?)key);
   }

   [DoesNotReturn]
   internal static void ThrowArgumentException(ExceptionResource resource)
   {
      throw GetArgumentException(resource);
   }

   [DoesNotReturn]
   internal static void ThrowArgumentException(ExceptionResource resource, ExceptionArgument argument)
   {
      throw GetArgumentException(resource, argument);
   }

   private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument)
   {
      return new ArgumentNullException(GetArgumentName(argument));
   }

   [DoesNotReturn]
   internal static void ThrowArgumentNullException(ExceptionArgument argument)
   {
      throw GetArgumentNullException(argument);
   }

   [DoesNotReturn]
   internal static void ThrowArgumentNullException(ExceptionResource resource)
   {
      throw new ArgumentNullException(GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowArgumentNullException(ExceptionArgument argument, ExceptionResource resource)
   {
      throw new ArgumentNullException(GetArgumentName(argument), GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
   {
      throw new ArgumentOutOfRangeException(GetArgumentName(argument));
   }

   [DoesNotReturn]
   internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
   {
      throw GetArgumentOutOfRangeException(argument, resource);
   }

   [DoesNotReturn]
   internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber,
                                                         ExceptionResource resource)
   {
      throw GetArgumentOutOfRangeException(argument, paramNumber, resource);
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException(ExceptionResource resource)
   {
      throw GetInvalidOperationException(resource);
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException(ExceptionResource resource, Exception e)
   {
      throw new InvalidOperationException(GetResourceString(resource), e);
   }

   [DoesNotReturn]
   internal static void ThrowSerializationException(ExceptionResource resource)
   {
      throw new SerializationException(GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowSecurityException(ExceptionResource resource)
   {
      throw new System.Security.SecurityException(GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowRankException(ExceptionResource resource)
   {
      throw new RankException(GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowNotSupportedException(ExceptionResource resource)
   {
      throw new NotSupportedException(GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowUnauthorizedAccessException(ExceptionResource resource)
   {
      throw new UnauthorizedAccessException(GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowObjectDisposedException(string objectName, ExceptionResource resource)
   {
      throw new ObjectDisposedException(objectName, GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowObjectDisposedException(ExceptionResource resource)
   {
      throw new ObjectDisposedException(null, GetResourceString(resource));
   }

   [DoesNotReturn]
   internal static void ThrowNotSupportedException()
   {
      throw new NotSupportedException();
   }

   [DoesNotReturn]
   internal static void ThrowAggregateException(List<Exception> exceptions)
   {
      throw new AggregateException(exceptions);
   }

   [DoesNotReturn]
   internal static void ThrowOutOfMemoryException()
   {
      throw new OutOfMemoryException();
   }

   [DoesNotReturn]
   internal static void ThrowArgumentException_Argument_InvalidArrayType()
   {
      throw new ArgumentException("Invalid array type.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_InvalidOperation_EnumNotStarted()
   {
      throw new InvalidOperationException("Enumeration has not started.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_InvalidOperation_EnumEnded()
   {
      throw new InvalidOperationException("Enumeration has ended.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_EnumCurrent(int index)
   {
      throw GetInvalidOperationException_EnumCurrent(index);
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
   {
      throw new InvalidOperationException("Collection was modified during enumeration.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
   {
      throw new InvalidOperationException("Invalid enumerator state: enumeration cannot proceed.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_InvalidOperation_NoValue()
   {
      throw new InvalidOperationException("No value provided.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported()
   {
      throw new InvalidOperationException("Concurrent operations are not supported.");
   }

   [DoesNotReturn]
   internal static void ThrowInvalidOperationException_HandleIsNotInitialized()
   {
      throw new InvalidOperationException("Handle is not initialized.");
   }

   [DoesNotReturn]
   internal static void ThrowFormatException_BadFormatSpecifier()
   {
      throw new FormatException("Bad format specifier.");
   }

   private static ArgumentException GetArgumentException(ExceptionResource resource)
   {
      return new ArgumentException(GetResourceString(resource));
   }

   private static InvalidOperationException GetInvalidOperationException(ExceptionResource resource)
   {
      return new InvalidOperationException(GetResourceString(resource));
   }

   private static ArgumentException GetWrongKeyTypeArgumentException(object? key, Type targetType)
   {
      return new ArgumentException($"Wrong key type. Expected {targetType}, got: '{key}'.", nameof(key));
   }

   private static ArgumentException GetWrongValueTypeArgumentException(object? value, Type targetType)
   {
      return new ArgumentException($"Wrong value type. Expected {targetType}, got: '{value}'.", nameof(value));
   }

   private static KeyNotFoundException GetKeyNotFoundException(object? key)
   {
      return new KeyNotFoundException($"Key not found: {key}");
   }

   private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(
      ExceptionArgument argument, ExceptionResource resource)
   {
      return new ArgumentOutOfRangeException(GetArgumentName(argument), GetResourceString(resource));
   }

   private static ArgumentException GetArgumentException(ExceptionResource resource, ExceptionArgument argument)
   {
      return new ArgumentException(GetResourceString(resource), GetArgumentName(argument));
   }

   private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(
      ExceptionArgument argument, int paramNumber, ExceptionResource resource)
   {
      return new ArgumentOutOfRangeException(GetArgumentName(argument) + "[" + paramNumber.ToString() + "]",
                                             GetResourceString(resource));
   }

   private static InvalidOperationException GetInvalidOperationException_EnumCurrent(int index)
   {
      return new InvalidOperationException(
         index < 0 ? "Enumeration has not started" : "Enumeration has ended");
   }

   // Allow nulls for reference types and Nullable<U>, but not for value types.
   // Aggressively inline so the jit evaluates the if in place and either drops the call altogether
   // Or just leaves null test and call to the Non-returning ThrowHelper.ThrowArgumentNullException
   [MethodImpl(MethodImplOptions.AggressiveInlining)]
   internal static void IfNullAndNullsAreIllegalThenThrow<T>(object? value, ExceptionArgument argName)
   {
      // Note that default(T) is not equal to null for value types except when T is Nullable<U>.
      if (!(default(T) == null) && value == null) ThrowHelper.ThrowArgumentNullException(argName);
   }

   [MethodImpl(MethodImplOptions.AggressiveInlining)]
   internal static void ThrowForUnsupportedVectorBaseType<T>() where T : struct
   {
      if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) &&
          typeof(T) != typeof(short) && typeof(T) != typeof(ushort) &&
          typeof(T) != typeof(int) && typeof(T) != typeof(uint) &&
          typeof(T) != typeof(long) && typeof(T) != typeof(ulong) &&
          typeof(T) != typeof(float) && typeof(T) != typeof(double)) {
         ThrowNotSupportedException(ExceptionResource.Arg_TypeNotSupported);
      }
   }

#if false // Reflection-based implementation does not work for CoreRT/ProjectN
        // This function will convert an ExceptionArgument enum value to the argument name string.
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string GetArgumentName(ExceptionArgument argument)
        {
            Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument),
                "The enum value is not defined, please check the ExceptionArgument Enum.");

            return argument.ToString();
        }
#endif

   private static string GetArgumentName(ExceptionArgument argument)
   {
      switch (argument) {
         case ExceptionArgument.obj:
            return "obj";
         case ExceptionArgument.dictionary:
            return "dictionary";
         case ExceptionArgument.array:
            return "array";
         case ExceptionArgument.info:
            return "info";
         case ExceptionArgument.key:
            return "key";
         case ExceptionArgument.text:
            return "text";
         case ExceptionArgument.values:
            return "values";
         case ExceptionArgument.value:
            return "value";
         case ExceptionArgument.startIndex:
            return "startIndex";
         case ExceptionArgument.task:
            return "task";
         case ExceptionArgument.ch:
            return "ch";
         case ExceptionArgument.s:
            return "s";
         case ExceptionArgument.input:
            return "input";
         case ExceptionArgument.list:
            return "list";
         case ExceptionArgument.index:
            return "index";
         case ExceptionArgument.capacity:
            return "capacity";
         case ExceptionArgument.collection:
            return "collection";
         case ExceptionArgument.item:
            return "item";
         case ExceptionArgument.converter:
            return "converter";
         case ExceptionArgument.match:
            return "match";
         case ExceptionArgument.count:
            return "count";
         case ExceptionArgument.action:
            return "action";
         case ExceptionArgument.comparison:
            return "comparison";
         case ExceptionArgument.exceptions:
            return "exceptions";
         case ExceptionArgument.exception:
            return "exception";
         case ExceptionArgument.enumerable:
            return "enumerable";
         case ExceptionArgument.start:
            return "start";
         case ExceptionArgument.format:
            return "format";
         case ExceptionArgument.culture:
            return "culture";
         case ExceptionArgument.comparer:
            return "comparer";
         case ExceptionArgument.comparable:
            return "comparable";
         case ExceptionArgument.source:
            return "source";
         case ExceptionArgument.state:
            return "state";
         case ExceptionArgument.length:
            return "length";
         case ExceptionArgument.comparisonType:
            return "comparisonType";
         case ExceptionArgument.manager:
            return "manager";
         case ExceptionArgument.sourceBytesToCopy:
            return "sourceBytesToCopy";
         case ExceptionArgument.callBack:
            return "callBack";
         case ExceptionArgument.creationOptions:
            return "creationOptions";
         case ExceptionArgument.function:
            return "function";
         case ExceptionArgument.delay:
            return "delay";
         case ExceptionArgument.millisecondsDelay:
            return "millisecondsDelay";
         case ExceptionArgument.millisecondsTimeout:
            return "millisecondsTimeout";
         case ExceptionArgument.timeout:
            return "timeout";
         case ExceptionArgument.type:
            return "type";
         case ExceptionArgument.sourceIndex:
            return "sourceIndex";
         case ExceptionArgument.sourceArray:
            return "sourceArray";
         case ExceptionArgument.destinationIndex:
            return "destinationIndex";
         case ExceptionArgument.destinationArray:
            return "destinationArray";
         case ExceptionArgument.other:
            return "other";
         case ExceptionArgument.newSize:
            return "newSize";
         case ExceptionArgument.lowerBounds:
            return "lowerBounds";
         case ExceptionArgument.lengths:
            return "lengths";
         case ExceptionArgument.len:
            return "len";
         case ExceptionArgument.keys:
            return "keys";
         case ExceptionArgument.indices:
            return "indices";
         case ExceptionArgument.endIndex:
            return "endIndex";
         case ExceptionArgument.elementType:
            return "elementType";
         case ExceptionArgument.arrayIndex:
            return "arrayIndex";
         default:
            Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum.");
            return argument.ToString();
      }
   }

#if false // Reflection-based implementation does not work for CoreRT/ProjectN
        // This function will convert an ExceptionResource enum value to the resource string.
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string GetResourceString(ExceptionResource resource)
        {
            Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource),
                "The enum value is not defined, please check the ExceptionResource Enum.");

            return SR.GetResourceString(resource.ToString());
        }
#endif

   private static string GetResourceString(ExceptionResource resource)
   {
      switch (resource) {
         case ExceptionResource.ArgumentOutOfRange_Index:
            return "Argument 'index' was out of the range of valid values.";
         case ExceptionResource.ArgumentOutOfRange_Count:
            return "Argument 'count' was out of the range of valid values.";
         case ExceptionResource.Arg_ArrayPlusOffTooSmall:
            return "Array plus offset too small.";
         case ExceptionResource.NotSupported_ReadOnlyCollection:
            return "This operation is not supported on a read-only collection.";
         case ExceptionResource.Arg_RankMultiDimNotSupported:
            return "Multi-dimensional arrays are not supported.";
         case ExceptionResource.Arg_NonZeroLowerBound:
            return "Arrays with a non-zero lower bound are not supported.";
         case ExceptionResource.ArgumentOutOfRange_ListInsert:
            return "Insertion index was out of the range of valid values.";
         case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum:
            return "The number must be non-negative.";
         case ExceptionResource.ArgumentOutOfRange_SmallCapacity:
            return "The capacity cannot be set below the current Count.";
         case ExceptionResource.Argument_InvalidOffLen:
            return "Invalid offset length.";
         case ExceptionResource.ArgumentOutOfRange_BiggerThanCollection:
            return "The given value was larger than the size of the collection.";
         case ExceptionResource.Serialization_MissingKeys:
            return "Serialization error: missing keys.";
         case ExceptionResource.Serialization_NullKey:
            return "Serialization error: null key.";
         case ExceptionResource.NotSupported_KeyCollectionSet:
            return "The KeyCollection does not support modification.";
         case ExceptionResource.NotSupported_ValueCollectionSet:
            return "The ValueCollection does not support modification.";
         case ExceptionResource.InvalidOperation_NullArray:
            return "Null arrays are not supported.";
         case ExceptionResource.InvalidOperation_HSCapacityOverflow:
            return "Set hash capacity overflow. Cannot increase size.";
         case ExceptionResource.NotSupported_StringComparison:
            return "String comparison not supported.";
         case ExceptionResource.ConcurrentCollection_SyncRoot_NotSupported:
            return "SyncRoot not supported.";
         case ExceptionResource.ArgumentException_OtherNotArrayOfCorrectLength:
            return "The other array is not of the correct length.";
         case ExceptionResource.ArgumentOutOfRange_EndIndexStartIndex:
            return "The end index does not come after the start index.";
         case ExceptionResource.ArgumentOutOfRange_HugeArrayNotSupported:
            return "Huge arrays are not supported.";
         case ExceptionResource.Argument_AddingDuplicate:
            return "Duplicate item added.";
         case ExceptionResource.Argument_InvalidArgumentForComparison:
            return "Invalid argument for comparison.";
         case ExceptionResource.Arg_LowerBoundsMustMatch:
            return "Array lower bounds must match.";
         case ExceptionResource.Arg_MustBeType:
            return "Argument must be of type: ";
         case ExceptionResource.InvalidOperation_IComparerFailed:
            return "IComparer failed.";
         case ExceptionResource.NotSupported_FixedSizeCollection:
            return "This operation is not suppored on a fixed-size collection.";
         case ExceptionResource.Rank_MultiDimNotSupported:
            return "Multi-dimensional arrays are not supported.";
         case ExceptionResource.Arg_TypeNotSupported:
            return "Type not supported.";
         default:
            Debug.Assert(false,
                         "The enum value is not defined, please check the ExceptionResource Enum.");
            return resource.ToString();
      }
   }
}

//
// The convention for this enum is using the argument name as the enum name
//
internal enum ExceptionArgument
{
   obj,
   dictionary,
   array,
   info,
   key,
   text,
   values,
   value,
   startIndex,
   task,
   ch,
   s,
   input,
   list,
   index,
   capacity,
   collection,
   item,
   converter,
   match,
   count,
   action,
   comparison,
   exceptions,
   exception,
   enumerable,
   start,
   format,
   culture,
   comparer,
   comparable,
   source,
   state,
   length,
   comparisonType,
   manager,
   sourceBytesToCopy,
   callBack,
   creationOptions,
   function,
   delay,
   millisecondsDelay,
   millisecondsTimeout,
   timeout,
   type,
   sourceIndex,
   sourceArray,
   destinationIndex,
   destinationArray,
   other,
   newSize,
   lowerBounds,
   lengths,
   len,
   keys,
   indices,
   endIndex,
   elementType,
   arrayIndex
}

//
// The convention for this enum is using the resource name as the enum name
//
internal enum ExceptionResource
{
   ArgumentOutOfRange_Index,
   ArgumentOutOfRange_Count,
   Arg_ArrayPlusOffTooSmall,
   NotSupported_ReadOnlyCollection,
   Arg_RankMultiDimNotSupported,
   Arg_NonZeroLowerBound,
   ArgumentOutOfRange_ListInsert,
   ArgumentOutOfRange_NeedNonNegNum,
   ArgumentOutOfRange_SmallCapacity,
   Argument_InvalidOffLen,
   ArgumentOutOfRange_BiggerThanCollection,
   Serialization_MissingKeys,
   Serialization_NullKey,
   NotSupported_KeyCollectionSet,
   NotSupported_ValueCollectionSet,
   InvalidOperation_NullArray,
   InvalidOperation_HSCapacityOverflow,
   NotSupported_StringComparison,
   ConcurrentCollection_SyncRoot_NotSupported,
   ArgumentException_OtherNotArrayOfCorrectLength,
   ArgumentOutOfRange_EndIndexStartIndex,
   ArgumentOutOfRange_HugeArrayNotSupported,
   Argument_AddingDuplicate,
   Argument_InvalidArgumentForComparison,
   Arg_LowerBoundsMustMatch,
   Arg_MustBeType,
   InvalidOperation_IComparerFailed,
   NotSupported_FixedSizeCollection,
   Rank_MultiDimNotSupported,
   Arg_TypeNotSupported,
}